#!/usr/bin/env bash

# Copyright 2021 The Tekton Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#     http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# dashboard flavour
OPENSHIFT="false"
READONLY="false"

# configuration default values
DEBUG="false"
INSTALL_NAMESPACE="tekton-pipelines"
PIPELINES_NAMESPACE="tekton-pipelines"
TRIGGERS_NAMESPACE="tekton-pipelines"
EXTENSIONS_RBAC="false"
LOGOUT_URL=""
LOG_LEVEL="info"
LOG_FORMAT="json"
TENANT_NAMESPACE=""
STREAM_LOGS="true"
EXTERNAL_LOGS=""
BASE_RELEASE_URL="https://storage.googleapis.com/tekton-releases/dashboard"

# additional options passed to ko resolve
KO_RESOLVE_OPTIONS=""

initOS() {
  OS=$(echo `uname`|tr '[:upper:]' '[:lower:]')

  case "$OS" in
    # Minimalist GNU for Windows
    mingw*) OS='windows';;
  esac

  debug "Detected OS: $OS"
}

verifySupported() {
  if ( [ "$ACTION" == "install" ] || [ "$ACTION" == "uninstall" ] ) && ! type "kubectl" > /dev/null 2>&1; then
    echo "kubectl is required"
    exit 1
  fi

  if [ -z "$DASHBOARD_VERSION" ] && ! type "kustomize" > /dev/null 2>&1; then
    echo "kustomize is required"
    exit 1
  fi

  if [ -z "$DASHBOARD_VERSION" ] && ! type "ko" > /dev/null 2>&1; then
    echo "ko is required"
    exit 1
  fi

  if ! type "sed" > /dev/null 2>&1; then
    echo "sed is required"
    exit 1
  fi

  if ! type "curl" > /dev/null 2>&1 && ! type "wget" > /dev/null 2>&1; then
    echo "Either curl or wget is required"
    exit 1
  fi
}

debug() {
  local message=$1

  if [ "$DEBUG" == "true" ]; then
    echo $message
  fi
}

compile() {
  local overlay="overlays/installer"

  if [ "$READONLY" == "true" ]; then
    overlay="$overlay/read-only"
  else
    overlay="$overlay/read-write"
  fi

  debug "Building overlay $overlay ..."
  kustomize build --load_restrictor none "$overlay" | ko resolve $KO_RESOLVE_OPTIONS -f - > "$TMP_FILE"
}

download() {
  if [ $DASHBOARD_VERSION == "latest" ]; then
    local url="$BASE_RELEASE_URL/latest/installer-"
  else
    local url="$BASE_RELEASE_URL/previous/$DASHBOARD_VERSION/installer-"
  fi

  url="${url}tekton-dashboard-release"

  if [ "$READONLY" == "true" ]; then
    url="${url}-readonly"
  fi

  url="${url}.yaml"

  debug "Downloading $url -> $TMP_FILE ..."

  if type "curl" > /dev/null 2>&1; then
    curl -s "$url" -o "$TMP_FILE"
  elif type "wget" > /dev/null 2>&1; then
    wget -q -O "$TMP_FILE" "$url"
  fi
}

setup() {
  if [ ! -z "$OVERRIDE_NAMESPACE" ]; then
    INSTALL_NAMESPACE="$OVERRIDE_NAMESPACE"
  elif [ "$OPENSHIFT" == "true" ]; then
    INSTALL_NAMESPACE="openshift-pipelines"
  fi

  if [ ! -z "$OVERRIDE_PIPELINES_NAMESPACE" ]; then
    PIPELINES_NAMESPACE="$OVERRIDE_PIPELINES_NAMESPACE"
  elif [ "$OPENSHIFT" == "true" ]; then
    PIPELINES_NAMESPACE="openshift-pipelines"
  fi

  if [ ! -z "$OVERRIDE_TRIGGERS_NAMESPACE" ]; then
    TRIGGERS_NAMESPACE="$OVERRIDE_TRIGGERS_NAMESPACE"
  elif [ "$OPENSHIFT" == "true" ]; then
    TRIGGERS_NAMESPACE="openshift-pipelines"
  fi
}

replace() {
  local src=$1
  local dest=$2

  debug "REPLACE $src -> $dest"

  if [ "$OS" == "darwin" ]; then
    sed -i "" "s~$src~$dest~g" $TMP_FILE
  else
    sed -i "s~$src~$dest~g" $TMP_FILE
  fi
}

patch() {
  replace "--pipelines-namespace=--pipelines-namespace" "--pipelines-namespace=$PIPELINES_NAMESPACE"
  replace "--triggers-namespace=--triggers-namespace" "--triggers-namespace=$TRIGGERS_NAMESPACE"
  replace "--log-level=--log-level" "--log-level=$LOG_LEVEL"
  replace "--log-format=--log-format" "--log-format=$LOG_FORMAT"
  replace "--logout-url=--logout-url" "--logout-url=$LOGOUT_URL"
  replace "--read-only=--read-only" "--read-only=$READONLY"
  replace "--namespace=--tenant-namespace" "--namespace=$TENANT_NAMESPACE"
  replace "--stream-logs=--stream-logs" "--stream-logs=$STREAM_LOGS"
  replace "--external-logs=--external-logs" "--external-logs=$EXTERNAL_LOGS"
  replace "namespace: tekton-dashboard" "namespace: $INSTALL_NAMESPACE"
}

rbac() {
cat <<EOF >> $TMP_FILE
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  labels:
    app.kubernetes.io/component: dashboard
    app.kubernetes.io/instance: default
    app.kubernetes.io/part-of: tekton-dashboard
  name: tekton-dashboard-pipelines
  namespace: $PIPELINES_NAMESPACE
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: tekton-dashboard-pipelines
subjects:
  - kind: ServiceAccount
    name: tekton-dashboard
    namespace: $INSTALL_NAMESPACE
EOF

cat <<EOF >> $TMP_FILE
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  labels:
    app.kubernetes.io/component: dashboard
    app.kubernetes.io/instance: default
    app.kubernetes.io/part-of: tekton-dashboard
  name: tekton-dashboard-dashboard
  namespace: $INSTALL_NAMESPACE
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: tekton-dashboard-dashboard
subjects:
  - kind: ServiceAccount
    name: tekton-dashboard
    namespace: $INSTALL_NAMESPACE
EOF

cat <<EOF >> $TMP_FILE
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  labels:
    app.kubernetes.io/component: dashboard
    app.kubernetes.io/instance: default
    app.kubernetes.io/part-of: tekton-dashboard
  name: tekton-dashboard-triggers
  namespace: $TRIGGERS_NAMESPACE
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: tekton-dashboard-triggers
subjects:
  - kind: ServiceAccount
    name: tekton-dashboard
    namespace: $INSTALL_NAMESPACE
EOF

if [ "$EXTENSIONS_RBAC" == "true" ]; then
cat <<EOF >> $TMP_FILE
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: tekton-dashboard-extensions
  labels:
    app.kubernetes.io/component: dashboard
    app.kubernetes.io/instance: default
    app.kubernetes.io/part-of: tekton-dashboard
aggregationRule:
  clusterRoleSelectors:
    - matchLabels:
        rbac.dashboard.tekton.dev/aggregate-to-dashboard: 'true'
EOF
fi

if [ ! -z "$TENANT_NAMESPACE" ]; then

cat <<EOF >> $TMP_FILE
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  labels:
    app.kubernetes.io/component: dashboard
    app.kubernetes.io/instance: default
    app.kubernetes.io/part-of: tekton-dashboard
  name: tekton-dashboard-tenant
  namespace: $TENANT_NAMESPACE
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: tekton-dashboard-tenant
subjects:
  - kind: ServiceAccount
    name: tekton-dashboard
    namespace: $INSTALL_NAMESPACE
EOF

if [ "$EXTENSIONS_RBAC" == "true" ]; then
cat <<EOF >> $TMP_FILE
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  labels:
    app.kubernetes.io/component: dashboard
    app.kubernetes.io/instance: default
    app.kubernetes.io/part-of: tekton-dashboard
  name: tekton-dashboard-extensions
  namespace: $TENANT_NAMESPACE
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: tekton-dashboard-extensions
subjects:
  - kind: ServiceAccount
    name: tekton-dashboard
    namespace: $INSTALL_NAMESPACE
EOF
fi

else

cat <<EOF >> $TMP_FILE
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  labels:
    app.kubernetes.io/component: dashboard
    app.kubernetes.io/instance: default
    app.kubernetes.io/part-of: tekton-dashboard
  name: tekton-dashboard-tenant
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: tekton-dashboard-tenant
subjects:
  - kind: ServiceAccount
    name: tekton-dashboard
    namespace: $INSTALL_NAMESPACE
EOF

if [ "$EXTENSIONS_RBAC" == "true" ]; then
cat <<EOF >> $TMP_FILE
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  labels:
    app.kubernetes.io/component: dashboard
    app.kubernetes.io/instance: default
    app.kubernetes.io/part-of: tekton-dashboard
  name: tekton-dashboard-extensions
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: tekton-dashboard-extensions
subjects:
  - kind: ServiceAccount
    name: tekton-dashboard
    namespace: $INSTALL_NAMESPACE
EOF
fi

fi
}

ingress() {
if [ ! -z "$INGRESS_URL" ] && [ ! -z "$INGRESS_SECRET" ]; then
cat <<EOF >> $TMP_FILE
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: tekton-dashboard
  namespace: $INSTALL_NAMESPACE
spec:
  tls:
  - hosts:
    - $INGRESS_URL
    secretName: $INGRESS_SECRET
  rules:
  - host: $INGRESS_URL
    http:
      paths:
      - pathType: ImplementationSpecific
        backend:
          service:
            name: tekton-dashboard
            port:
              number: 9097
EOF
elif [ ! -z "$INGRESS_URL" ]; then
cat <<EOF >> $TMP_FILE
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: tekton-dashboard
  namespace: $INSTALL_NAMESPACE
spec:
  rules:
  - host: $INGRESS_URL
    http:
      paths:
      - pathType: ImplementationSpecific
        backend:
          service:
            name: tekton-dashboard
            port:
              number: 9097
EOF
fi
}

# install invokes kubectl apply with the built manifest.
install() {
  echo "Installing ..."

  if [ ! -z "$TENANT_NAMESPACE" ]; then
    kubectl create ns $TENANT_NAMESPACE > /dev/null 2>&1 || true
  fi

  kubectl create ns $INSTALL_NAMESPACE > /dev/null 2>&1 || true
  kubectl apply -f $TMP_FILE
}

uninstall() {
  echo "Uninstalling ..."
  resourceKinds=(
    "deployments.apps"
    "serviceaccounts"
    "services"
    "customresourcedefinitions.apiextensions.k8s.io"
    "clusterrolebindings.rbac.authorization.k8s.io"
    "clusterroles.rbac.authorization.k8s.io"
    "rolebindings.rbac.authorization.k8s.io"
    "roles.rbac.authorization.k8s.io"
  )
  for resourceKind in "${resourceKinds[@]}";do
    echo "Deleting ${resourceKind}"
    kubectl delete ${resourceKind} --ignore-not-found -l=app.kubernetes.io/instance=default,app.kubernetes.io/part-of=tekton-dashboard --all-namespaces
  done
}

build() {
  if [ -z "$OUTPUT_FILE" ]; then
    cat $TMP_FILE
  else
    cat $TMP_FILE > $OUTPUT_FILE
  fi
}

# fail_trap is executed if an error occurs.
fail_trap() {
  result=$?
  cleanup
  exit $result
}

# help provides possible cli installation arguments
help () {
  echo -e "Global command syntax:"
  echo -e "\tinstaller COMMAND [OPTIONS]"
  echo -e ""
  echo -e "Accepted commands:"
  echo -e "\thelp|h\t\t\t\t\tPrints this help"
  echo -e "\tinstall|i\t\t\t\tInstalls the dashboard"
  echo -e "\tuninstall|u\t\t\t\tUninstalls the dashboard"
  echo -e "\tbuild|b\t\t\t\t\tBuilds the manifests and dashboard docker image"
  echo -e "\trelease|r\t\t\t\tBuilds the manifests and dashboard docker image for release"
  echo -e ""
  echo -e "Accepted options:"
  echo -e "\t[--debug]\t\t\t\tPrints additional messages in the console"
  echo -e "\t[--extensions-rbac]\t\t\tEnable ClusterRole aggregation for easier management of extensions RBAC"
  echo -e "\t[--external-logs <logs-provider-url>]\tExternal URL from which to fetch logs when logs are not available in the cluster"
  echo -e "\t[--ingress-secret <secret>]\t\tWill add SSL support to the ingress"
  echo -e "\t[--ingress-url <url>]\t\t\tWill create an additional ingress with the specified URL"
  echo -e "\t[--log-format <log-format>]\t\tSpecifies the log format (json or console), default is json"
  echo -e "\t[--log-level <log-level>]\t\tSpecifies the log level (debug, info, warn, error, dpanic, panic, fatal), default is info"
  echo -e "\t[--logout-url <logout-url>]\t\tWill set up the logout URL"
  echo -e "\t[--namespace <namespace>]\t\tWill override install namespace"
  echo -e "\t[--nightly]\t\t\t\tWill download manifests from the nightly releases channel"
  echo -e "\t[--openshift]\t\t\t\tWill build manifests for openshift"
  echo -e "\t[--output <file>]\t\t\tWill output built manifests in the file instead of in the console"
  echo -e "\t[--pipelines-namespace <namespace>]\tOverride the namespace where Tekton Pipelines is installed (defaults to tekton-pipelines)"
  echo -e "\t[--platform <platform>]\t\t\tOverride the platform to build for"
  echo -e "\t[--read-only]\t\t\t\tWill build manifests for a readonly deployment"
  echo -e "\t[--stream-logs false]\t\t\t\tWill disable log streaming and use polling instead"
  echo -e "\t[--tenant-namespace <namespace>]\tWill limit the visibility to the specified namespace only"
  echo -e "\t[--triggers-namespace <namespace>]\tOverride the namespace where Tekton Triggers is installed (defaults to tekton-pipelines)"
  echo -e "\t[--version <version>]\t\t\tWill download manifests for specified version or build everything using kustomize/ko"
}

# cleanup temporary files
cleanup() {
  if [[ -d "${TMP_ROOT:-}" ]]; then
    rm -rf "$TMP_ROOT"
  fi
}

# Execution

#Stop execution on any error
trap "fail_trap" EXIT

set -e

# Parsing command
case $1 in
  'help'|h)
    help
    exit 0
    ;;
  'install'|i)
    ACTION="install"
    shift
    ;;
  'uninstall'|u)
    uninstall
    exit 0
    ;;
  'build'|b)
    ACTION="build"
    shift
    ;;
  'release'|r)
    KO_RESOLVE_OPTIONS="--preserve-import-paths --platform=all"
    ACTION="build"
    shift
    ;;
  *)
    ACTION="build"
    ;;
esac

set -u

# Parsing options (if any)

while [[ $# -gt 0 ]]; do
  case $1 in
    '--version')
      shift
      DASHBOARD_VERSION="${1}"
      ;;
    '--nightly')
      BASE_RELEASE_URL="https://storage.googleapis.com/tekton-releases-nightly/dashboard"
      ;;
    '--openshift')
      OPENSHIFT="true"
      ;;
    '--read-only')
      READONLY="true"
      ;;
    '--stream-logs')
      shift
      STREAM_LOGS="${1}"
      ;;
    '--external-logs')
      shift
      EXTERNAL_LOGS="${1}"
      ;;
    '--namespace')
      shift
      OVERRIDE_NAMESPACE="${1}"
      ;;
    '--extensions-rbac')
      EXTENSIONS_RBAC="true"
      ;;
    '--log-format')
      shift
      LOG_FORMAT="${1}"
      ;;
    '--log-level')
      shift
      LOG_LEVEL="${1}"
      ;;
    '--logout-url')
      shift
      LOGOUT_URL="${1}"
      ;;
    '--pipelines-namespace')
      shift
      OVERRIDE_PIPELINES_NAMESPACE="${1}"
      ;;
    '--triggers-namespace')
      shift
      OVERRIDE_TRIGGERS_NAMESPACE="${1}"
      ;;
    '--tenant-namespace')
      shift
      TENANT_NAMESPACE="${1}"
      ;;
    '--ingress-url')
      shift
      INGRESS_URL="${1}"
      ;;
    '--ingress-secret')
      shift
      INGRESS_SECRET="${1}"
      ;;
    '--debug')
      DEBUG="true"
      ;;
    '--output')
      shift
      OUTPUT_FILE="${1}"
      ;;
    '--platform')
      shift
      KO_RESOLVE_OPTIONS="$KO_RESOLVE_OPTIONS --platform=${1}"
      ;;
    *)
      echo "ERROR: Unknown option $1"
      help
      exit 1
      ;;
  esac
  shift
done

set +u

TMP_ROOT="$(mktemp -dt tekton-dashboard-installer.XXXXXX)"
TMP_FILE="$TMP_ROOT/manifest.yaml"

initOS
verifySupported
setup
if [ -z "$DASHBOARD_VERSION" ]; then
  compile
else
  download
fi
patch
rbac
ingress
eval $ACTION
cleanup
